package drds.configuration.db.table_mapping;

import drds.configuration.common.Constants;
import drds.configuration.db.Db;
import drds.configuration.db.JdbcUtil;
import drds.configuration.db.transaction.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.*;
import java.util.Date;
import java.util.*;
import java.util.Map.Entry;

/**
 * Model和TableInfo组合,建议使用注解配置 <br/>
 * 更新操作都是操作主数据库,读操作默认是主数据库,如果设置了从数据库则采用负载均衡模式
 */
public abstract class Model<M extends Model<?>> {

    public static Sql sql = new SqlImpl();
    public static Logger logger = LoggerFactory.getLogger(Model.class);
    /**
     * 没有任何对象
     */
    private static Object[] objects = new Object[0];
    /**
     * 构建表信息关系
     */
    public TableInfo tableInfo;
    /**
     * 实例变量Map--通过get方法被页面访问
     */
    private Map<String, Object> M = new HashMap<String, Object>();// 修改底层的时候不能进行修改-否则导致外面去数据的时候有问题
    /**
     * 修改过的实例变量的Set
     */
    private Set<String> modifiedSet = new HashSet<String>();

    /**
     * 默认使用主数据库
     */

    public Model setGroupDataSourceIdAndMasterSlaveInfo() {
        TableInfo tableInfo = getTableInfo();
        Db.setDataSourceId(tableInfo.getDataSourceId());
        return this;
    }

    public Model removeGroupDataSourceIdAndMasterSlaveInfo() {
        Db.removeGroupDataSourceIdAndMasterSlaveInfo();
        return this;
    }

    public TableInfo getTableInfo() {
        if (this.tableInfo != null) {
            return tableInfo;
        } else {
            this.tableInfo = ModelClassToTableInfoMap.getTableInfo(getClass());
            return tableInfo;
        }
    }

    public Model<M> set(String key, Object value) {
        TableInfo tableInfo = getTableInfo();
        if (tableInfo.getColumnNameToClassTypeMap().containsKey(key)) {
            M.put(key, value);
            if (!tableInfo.getPrimaryKey().equals(key)) {
                modifiedSet.add(key);
            }
            Field field = getTableInfo().fieldColumnNameToFieldMap.get(key);
            try {
                field.setAccessible(true);
                field.set(this, value);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        } else {
            throw new RuntimeException("参数key:" + key + "不存在");
        }
        return this;
    }

    /**
     * 获取底层的map
     */
    public Map<String, Object> getMap() {
        return M;
    }

    /**
     * ============================================================
     * set操作
     * ============================================================
     */
    public Model<M> setMap(Map<String, Object> map) {
        for (Entry<String, Object> entry : map.entrySet()) {
            set(entry.getKey(), entry.getValue());
        }
        return this;
    }

    // 删除Model.属性
    public Model<M> clear() {
        M.clear();
        clearModifiedSet();
        return this;
    }
    //

    /**
     * 只能同包访问这个方法-防止使用的人调用此方法造成更新数据的时候会出现问题
     */
    void clearModifiedSet() {
        modifiedSet.clear();
    }

    private Set<String> getModifiedSet() {
        return modifiedSet;
    }

    /**
     * 获取底层的map Copy
     */
    public Map<String, Object> copyMap() {
        return new HashMap<String, Object>(M);
    }

    /**
     * 添加不是本Model中对应的table_info字段的key_value对
     *
     * @param key   不是本Model中对应的table_info字段
     * @param value Object
     */
    public Model<M> addOtherKey(String key, Object value) {
        M.put(key, value);
        return this;
    }

    /**
     * 打印该表字段类型
     */
    public void printColumnMapInfo() {
        TableInfo tableInfo = getTableInfo();
        tableInfo.printColumnMapInfo();
    }

    /**
     * <pre>
     * ============================================================
     * 数据库操作
     * ============================================================
     * </pre>
     */
    //从实体对象中获取数据
    public Map<String, Object> extract(Model model, TableInfo tableInfo) {
        Map<String, Field> fieldColumnNameToFieldMap = tableInfo.fieldColumnNameToFieldMap;
        Map<String, Object> M = new HashMap<String, Object>();
        for (Map.Entry<String, Field> entry : fieldColumnNameToFieldMap.entrySet()) {
            try {
                Field field = entry.getValue();
                //field.setAccessible(true);
                Object object = field.get(model);
                if (object != null) {
                    M.put(entry.getKey(), object);
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        return M;
    }

    public void difference(Map<String, Object> mapFromObject, Map<String, Object> M) {
        //需要针对每个属性以及对应的数据类型进行判断
        getModifiedSet().clear();
        Map<String, Class<?>> fieldColumnNameToClassTypeMap = getTableInfo().fieldColumnNameToClassTypeMap;
        for (Entry<String, Class<?>> entry : fieldColumnNameToClassTypeMap.entrySet()) {
            Class<?> classType = entry.getValue();
            Object objectFromObject = mapFromObject.get(entry.getKey());
            Object objectFromModel = M.get(entry.getKey());
            boolean change = false;
            if (objectFromModel == null && objectFromObject == null) {
                //not change
            } else if (objectFromModel == null && objectFromObject != null) {
                //change
                change = true;
            } else if (objectFromModel != null && objectFromObject == null) {
                //change
                change = true;
            } else if (objectFromModel != null && objectFromObject != null) {
                //change? 根据类型进行比较
                if (classType.getName().equals(Boolean.class.getName())) {
                    change = ((Boolean) objectFromModel).booleanValue() != ((Boolean) objectFromObject).booleanValue();
                } else if (classType.getName().equals(Short.class.getName())) {
                    change = ((Short) objectFromModel).shortValue() != ((Short) objectFromObject).shortValue();
                } else if (classType.getName().equals(Integer.class.getName())) {
                    change = ((Integer) objectFromModel).intValue() != ((Integer) objectFromObject).intValue();
                } else if (classType.getName().equals(Long.class.getName())) {
                    change = ((Long) objectFromModel).longValue() != ((Long) objectFromObject).longValue();
                } else if (classType.getName().equals(BigInteger.class.getName())) {
                    change = ((BigInteger) objectFromModel).compareTo(((BigInteger) objectFromObject)) != 0;
                } else if (classType.getName().equals(Float.class.getName())) {
                    change = ((Float) objectFromModel).floatValue() != ((Float) objectFromObject).floatValue();
                } else if (classType.getName().equals(Double.class.getName())) {
                    change = ((Double) objectFromModel).doubleValue() != ((Double) objectFromObject).doubleValue();
                } else if (classType.getName().equals(String.class.getName())) {
                    change = !objectFromModel.equals(objectFromObject);
                } else if (classType.getName().equals(BigDecimal.class.getName())) {
                    change = ((BigDecimal) objectFromModel).compareTo(((BigDecimal) objectFromObject)) != 0;
                } else if (classType.getName().equals(Date.class.getName())) {
                    change = ((Date) objectFromModel).compareTo(((Date) objectFromObject)) != 0;
                } else if (classType.getName().equals(java.sql.Date.class.getName())) {
                    change = ((java.sql.Date) objectFromModel).compareTo(((java.sql.Date) objectFromObject)) != 0;
                } else if (classType.getName().equals(java.sql.Time.class.getName())) {
                    change = ((Time) objectFromModel).compareTo(((Time) objectFromObject)) != 0;
                } else if (classType.getName().equals(java.sql.Timestamp.class.getName())) {
                    change = ((Timestamp) objectFromModel).compareTo(((Timestamp) objectFromObject)) != 0;
                } else {
                    throw new RuntimeException("类型不支持");
                }
            }
            if (change) {
                getModifiedSet().add(entry.getKey());
            }

        }
    }

    /**
     * 保存Model信息:注意保存信息后原来的信息马上清空
     */
    public boolean save() {
        TableInfo tableInfo = getTableInfo();
        //M,有可能采用AR编程也有可能采用对象模式进行编程,最好是底层由对象编程转为AR编程,那么涉及到一个转换机制
        Map<String, Object> mapFromObject = extract(this, tableInfo);
        M.putAll(mapFromObject);
        //
        StringBuilder sql = new StringBuilder();
        List<Object> parameterList = new ArrayList<Object>();
        Model.sql.save(tableInfo, M, sql, parameterList);
        if (Constants.devMode) {
            logger.debug("sql:" + sql);
        }
        String dataSourceId = tableInfo.getDataSourceId();
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        int update = 0;
        try {
            connection = Transaction.getConnectionFromTransactionContextIfTransactioning(dataSourceId);
            boolean isSupportAutoIncrementKey = Model.sql.isSupportAutoIncrementKey();
            if (isSupportAutoIncrementKey) {
                preparedStatement = connection.prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS);
            } else {
                preparedStatement = connection.prepareStatement(sql.toString());
            }
            int size = parameterList.size();
            for (int i = 0; i < size; i++) {
                preparedStatement.setObject(i + 1, parameterList.get(i));
            }
            update = preparedStatement.executeUpdate();
            if (isSupportAutoIncrementKey) {
                getGeneratedKey(preparedStatement, tableInfo);
            }
            getModifiedSet().clear();
            return update >= 1;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            JdbcUtil.close(preparedStatement);
            Transaction.close(connection);
        }
    }

    private void getGeneratedKey(PreparedStatement preparedStatement, TableInfo tableInfo) throws SQLException {
        String primaryKey = tableInfo.getPrimaryKey();
        if (get(primaryKey) == null) {
            // 这句话会自动获取主键
            ResultSet resultSet = preparedStatement.getGeneratedKeys();
            if (resultSet.next()) {
                Class columnType = tableInfo.getColumnType(primaryKey);
                if (columnType == Integer.class || columnType == int.class) {
                    set(primaryKey, resultSet.getInt(1));
                } else if (columnType == Long.class || columnType == long.class) {
                    set(primaryKey, resultSet.getLong(1));
                } else {
                    set(primaryKey, resultSet.getObject(1));
                }
                resultSet.close();
            }
        }
    }

    public boolean update() {
        if (getModifiedSet().isEmpty()) {
            if (Constants.devMode) {
                logger.debug("没有什么可以用于更新的字段");
            }
            return true;
        }
        TableInfo tableInfo = getTableInfo();
        String primaryKey = tableInfo.getPrimaryKey();
        Object id = M.get(primaryKey);
        if (id == null) {
            throw new RuntimeException("执行更新操作时缺少主键值");
        }


        Map<String, Object> mapFromObject = extract(this, tableInfo);//查询出来的时候需要更新到属性上面,更新的时候需要同步到M上面.
        //需要和以前的进行对比出差异字段
        difference(mapFromObject, M);
        M.putAll(mapFromObject);
        //
        StringBuilder sql = new StringBuilder();
        List<Object> parameterList = new ArrayList<Object>();
        Model.sql.update(tableInfo, M, getModifiedSet(), primaryKey, id, sql, parameterList);
        if (Constants.devMode) {
            logger.debug("sql:" + sql);
        }
        String dataSourceId = tableInfo.getDataSourceId();
        Connection connection = null;
        try {
            connection = Transaction.getConnectionFromTransactionContextIfTransactioning(dataSourceId);
            int update = JdbcUtil.update(connection, sql.toString(), parameterList.toArray());
            getModifiedSet().clear();
            if (update >= 1) {
                clearModifiedSet();
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {

            Transaction.close(connection);
        }
    }

    public boolean delete() {
        TableInfo tableInfo = getTableInfo();
        String primaryKey = tableInfo.getPrimaryKey();
        //只是提取主键信息
        Map<String, Object> mapFromObject = extract(this, tableInfo);
        M.put(primaryKey, mapFromObject.get(primaryKey));
        Object id = M.get(primaryKey);
        if (id == null) {
            throw new RuntimeException("主键不存在");
        }
        return deleteById(tableInfo, id);
    }

    public boolean deleteById(Object id) {
        if (id == null) {
            throw new NullPointerException("主键不存在");
        }
        TableInfo tableInfo = getTableInfo();
        return deleteById(tableInfo, id);
    }

    private boolean deleteById(TableInfo tableInfo, Object id) {
        String sql = Model.sql.deleteById(tableInfo);
        String dataSourceId = tableInfo.getDataSourceId();
        Db.setDataSourceId(dataSourceId);
        return Db.update(sql, id) >= 1;

    }

    /**
     * 检查sql是否有Model与之对应的表名
     */
    private void checkTableName(String sql) {
        TableInfo tableInfo = getTableInfo();
        if (!sql.toLowerCase().contains(tableInfo.getTableName().toLowerCase())) {
            throw new RuntimeException("sql[" + sql + "]语句中不包含表名:" + tableInfo.getTableName());
        }
    }

    // 根据主键查询
    public M findById(Object id) {
        return findById(id, "*");
    }

    /**
     * <pre>
     * 根据ID主键查询出columns中字段的数据信息 Example: User user = User.dao.findById(15, "name, age");
     *
     * @param id  主键
     * @param columns  内部用","分割
     * </pre>
     */
    public M findById(Object id, String columns) {
        TableInfo tableInfo = getTableInfo();
        String sql = Model.sql.findById(tableInfo, columns);
        List<M> list = find(sql, id);
        return list.size() > 0 ? list.get(0) : null;
    }

    public List<M> find(String sql) {
        return find(sql, objects);
    }

    public List<M> find(String sql, Object... parameters) {
        Map<String, Object> map = Db.$.get();
        //
        String dataSourceId = null;
        if (map == null) {
            TableInfo tableInfo = getTableInfo();
            dataSourceId = tableInfo.getDataSourceId();
        } else {
            dataSourceId = (String) map.get(Db.data_source_id);
        }
        Connection connection = null;
        try {
            connection = Transaction.getConnectionFromTransactionContextIfTransactioning(dataSourceId);
            if (Constants.devMode) {
                // 有可能在运行阶段的SQL语句不是操作这张表
                checkTableName(sql);
            }
            return find(getClass(), connection, sql, parameters);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            Transaction.close(connection);
        }
    }

    // 查询
    public List<M> find(Class<? extends Model> modelClass, Connection connection, String sql, Object... parameters) throws Exception {
        if (Constants.devMode) {
            logger.debug("sql:" + sql);
        }
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        int length = parameters.length;
        for (int i = 0; i < length; i++) {
            preparedStatement.setObject(i + 1, parameters[i]);
        }
        ResultSet resultSet = preparedStatement.executeQuery();
        List<M> list = ModelBuilder.build(resultSet, modelClass);
        JdbcUtil.close(resultSet, preparedStatement);
        return list;
    }

    /**
     * 得到结果集第一条。建议在SQL语句中添加 "limit 1".
     */
    public M findFirst(String sql) {
        List<M> result = find(sql, objects);
        if (result == null) {
            return null;
        } else {
            return result.size() > 0 ? result.get(0) : null;
        }
    }

    public M findFirst(String sql, Object... parameters) {
        List<M> list = find(sql, parameters);
        return list.size() > 0 ? list.get(0) : null;
    }

    /**
     * <pre>
     * ============================================================
     * 针对数据库结果集M取值
     * ============================================================
     * </pre>
     */
    public <T> T get(String key) {
        return (T) M.get(key);
    }

    public <T> T get(String key, Object defaultValue) {
        Object result = M.get(key);
        return (T) (result != null ? result : defaultValue);
    }

    /**
     * 处理tinyint(1)
     */
    public Boolean getBoolean(String key) {
        return (Boolean) M.get(key);
    }

    public Integer getInt(String key) {
        return (Integer) M.get(key);
    }

    private Float getFloat(String key) {
        return (Float) M.get(key);
    }

    private Double getDouble(String key) {
        return (Double) M.get(key);
    }

    public Long getLong(String key) {
        return (Long) M.get(key);
    }

    public java.util.Date getMillisecondsToDate(String key) {
        long milliseconds = (Long) M.get(key);
        return new java.util.Date(milliseconds);
    }

    public BigInteger getBigInteger(String key) {
        return (BigInteger) M.get(key);
    }

    public String getString(String key) {
        return (String) M.get(key);
    }

    public java.math.BigDecimal getBigDecimal(String key) {
        return (java.math.BigDecimal) M.get(key);
    }

    public java.util.Date getDate(String key) {
        return (java.util.Date) M.get(key);
    }


}
